from abc import ABC, abstractmethod
import collections
import sys
from concurrent.futures import ThreadPoolExecutor
import threading
import json
import re
import time
import tkinter as tk
import uuid
from ....utils.types import *
from ....apps.uds_ui.server_lib import Serve_Func
from datetime import datetime
from ....utils.loggers import ic_debugger
from ....globals import GLOBAL_UDS_SERVE_FUNC_LIB


class FunctestBase_Uds(ABC, tk.Toplevel):
    """
    基础抽象类
    """
    _objDict = {}

    def __new__(cls, PosId, *args, **kwargs):
        '''
        同一个PosId只能有一个实例，避免窗口多开出现问题
        '''
        PosId = PosId
        if PosId not in cls._objDict:
            newObj = object.__new__(cls)
            return newObj
        else:
            cls._objDict[PosId].resetTestState()
            cls._objDict[PosId].deiconify()
            return cls._objDict[PosId]

    def resetTestState(self):
        # 将上一次测试的展示状态,重置
        for key, data in enumerate(self.testDatas):
            self.sendDataLabelDict[key].configure(
                text='sendstate', background='gray')
            self.recvDataLabelDict[key].configure(
                text='recvstate', background='gray')
            self.alltestResMsg.config(foreground='gray', text='批量测试结果')

    def __init__(
        self, masterUI, parentWin, auto_unlock, PosId="xy", name="XXXX模块demo", MsgId=0xFFF, rcevId=0x999, Learmask=0xa5a5a5a5, testDatas=None
    ) -> None:
        # 如果之前已经实例化过，就不在进行初始化。
        # 否则就进行后面的初始化，并且在最后把该示例放到示例的字典_objDict中
        if PosId in self._objDict:
            return
        else:
            self._objDict[PosId] = self
        """
        root 是主窗口的实例self
        """
        super().__init__()
        # 给父窗口设定个醒目的变量名
        self.PARENT = parentWin
        self.masterUI = masterUI
        self.safetyAccess_Flag = auto_unlock
        self.title(f"测试控制面板----{name}-{PosId}")

        self.name = name
        self.PosId = PosId
        # 设定id值的随时跟踪变化
        self.MsgId = MsgId
        self.idIntVar = tk.IntVar()
        self.idIntVar.set(self.MsgId)
        # 设定反馈报文的id号
        self.rcevId = rcevId
        self.idRcevIntvar = tk.IntVar()
        self.idRcevIntvar.set(self.rcevId)
        # Learmask
        self.Learmask = Learmask
        self.idIntLearmask = tk.IntVar()
        self.idIntLearmask.set(self.Learmask)

        # 设定只有一个所有线程共用的线程的线程池，避免多线程交叉执行导致的数据异常
        # 每个单元测试下面的每个key代表的功能测试所用的线程池
        self.testSinglePoolUnitKey = ThreadPoolExecutor(
            max_workers=1, thread_name_prefix="FunctestBase_testSinglePoolUnitKey")
        # 单元测试allTest所用的线程池
        self.testSinglePoolUnit = ThreadPoolExecutor(
            max_workers=1, thread_name_prefix="FunctestBase_testSinglePoolUnit")

        if testDatas is not None:
            self.testDatas = testDatas
        else:
            self.testDatas = [
                UnitTestData_Type(name="name1", send="senddata1",
                                  rcev="recvdata1"),
                UnitTestData_Type(name="name2", send="senddata2",
                                  rcev="recvdata2"),
                UnitTestData_Type(name="name3", send="senddata3",
                                  rcev="recvdata3"),
                UnitTestData_Type(name="name4", send="senddata4",
                                  rcev="recvdata4"),
            ]

        # 前面设定完了基础值,然后再做gui的初始化
        self.viewInit()
        self.protocol('WM_DELETE_WINDOW', self.closeHandle)

    def closeHandle(self):
        # 把关闭窗口的destroy改为withdraw,避免关闭窗口后的在开启报错.
        # tkinter.messagebox.showwarning('关闭窗口', '您将关闭窗口self...')
        self.withdraw()

    @abstractmethod
    def viewInit(self, *args, **kwargs):
        pass

    @abstractmethod
    def sendControlMsg(self, *args, **kwargs):
        pass


class FunctestTemplate_Uds(FunctestBase_Uds):

    def viewInit(self):
        self.opFrame = tk.Frame(self, borderwidth=2, relief="raise")
        self.opFrame.grid(row=1, column=1)

        if 1:
            self.opFrameSub1 = tk.Frame(
                self.opFrame, borderwidth=1, relief="groove")
            self.opFrameSub1.grid(row=1, column=0)
            self.alltestBtn = tk.Button(
                self.opFrameSub1,
                text="批量测试",
                # TODO: 字体更改
                # font=("Arail", 16),
                command=self.allTest,
            )
            self.alltestBtn.grid(row=0, column=0)
            self.alltestResMsg = tk.Message(
                self.opFrameSub1, width=200, foreground='gray', text='批量测试结果')
            self.alltestResMsg.grid(row=0, column=1)

        # 单元测试frame
        if 1:
            self.opFrameSub2 = tk.Frame(
                self.opFrame, borderwidth=1, relief="groove")
            self.opFrameSub2.grid(row=2, column=0)

            self.sendDataEntryDict = {}
            self.sendDataStringDict = {}
            self.sendDataBtnDict = {}
            self.sendDataLabelDict = {}
            self.recvDataLabelDict = {}
            self.rcevDataStringDict = {}
            self.rcevDataEntryDict = {}

            # 用一个代用key的字典,通过for循环,把每个测试数据与设定的控件关联起来
            self.testDataDict = {}
            for key, data in enumerate(self.testDatas):
                # 发送数据设定
                # print(f'[file:{__file__}]=>[line:{(sys._getframe().f_lineno)}]==>key:{
                #   key}, data:{data}')
                self.testDataDict[key] = data
                self.sendDataStringDict[key] = tk.StringVar()
                self.sendDataStringDict[key].set(data['send'])
                self.sendDataEntryDict[key] = tk.Entry(
                    self.opFrameSub2,
                    width=20,
                    state="disabled",
                    textvariable=self.sendDataStringDict[key],
                )
                self.sendDataEntryDict[key].grid(
                    row=key, column=0, pady=5, padx=20)
                # self.sendDataEntryDict[key].bind(
                #     "<KeyRelease>", self.validateMsgData)

                self.sendDataBtnDict[key] = tk.Button(
                    self.opFrameSub2,
                    text=data['name'],
                    command=self.sendControlMsg(key),
                )
                self.sendDataBtnDict[key].grid(
                    row=key, column=1, pady=5, padx=20)

                self.sendDataLabelDict[key] = tk.Label(
                    self.opFrameSub2, text="sendstate")
                self.sendDataLabelDict[key].grid(
                    row=key, column=2, pady=5, padx=20)

                self.recvDataLabelDict[key] = tk.Label(
                    self.opFrameSub2, text="rcevstate")
                self.recvDataLabelDict[key].grid(
                    row=key, column=3, pady=5, padx=20)

                self.rcevDataStringDict[key] = tk.StringVar()
                self.rcevDataStringDict[key].set(data['rcev'])
                self.rcevDataEntryDict[key] = tk.Entry(
                    self.opFrameSub2,
                    width=20,
                    state="disabled",
                    textvariable=self.rcevDataStringDict[key],
                )
                self.rcevDataEntryDict[key].grid(
                    row=key, column=4, pady=5, padx=20)
                # self.rcevDataEntryDict[key].bind(
                #     "<KeyRelease>", self.validateMsgData)

        # 手动操作安全访问
        if 1:
            self.safetyAccess_manual_Frame = tk.Frame(
                self, borderwidth=2, relief="raise")
            self.safetyAccess_manual_Frame.grid(row=3, column=1)
            self.safetyAccess_manual_openBtn = tk.Button(self.safetyAccess_manual_Frame, text="手动开启安全访问",
                                                         command=self.manual_open_safetyAccess)
            self.safetyAccess_manual_openBtn.grid(row=1, column=1)
            self.safetyAccess_manual_closeBtn = tk.Button(self.safetyAccess_manual_Frame, text="关闭安全访问",
                                                          command=self.manual_close_safetyAccess)
            self.safetyAccess_manual_closeBtn.grid(row=1, column=2)

            pass

        if 1:
            self.configFrame = tk.Frame(self, borderwidth=2, relief="raise")
            self.configFrame.grid(row=4, column=1)
            self.enableEntrysBtn = tk.Button(
                self.configFrame, text="输入框开启", command=self.enableEntrysBtnCmd
            )
            self.enableEntrysBtn.pack(side="left")
            self.disableEntrysBtn = tk.Button(
                self.configFrame, text="输入框屏蔽", command=self.disableEntrysBtnCmd
            )
            self.disableEntrysBtn.pack(side="left")
            self.configBtn = tk.Button(
                self.configFrame, text="设置测试全局参数", command=self.initConfigPanel
            ).pack(side="left")

        # DOC 安全访问解锁
        # 通过设置一个标志位，来控制是否开启安全访问，后持续保持扩展会话
        # 如果要退出安全访问，只需要把标志位设置为False,退出扩展模式即可
        self.open_safetyAccess()

    def manual_open_safetyAccess(self):
        # 避免重复开启解锁程序
        if self.safetyAccess_Flag is True:
            return
        else:
            self.safetyAccess_Flag = True
            self.open_safetyAccess()

    def manual_close_safetyAccess(self):
        self.close_safetyAccess()

    def enableEntrysBtnCmd(self):
        for _, entry in self.sendDataEntryDict.items():
            entry.configure(state="normal")
        for _, entry in self.rcevDataEntryDict.items():
            entry.configure(state="normal")

    def disableEntrysBtnCmd(self):
        for _, entry in self.sendDataEntryDict.items():
            entry.configure(state="disabled")
        for _, entry in self.rcevDataEntryDict.items():
            entry.configure(state="disabled")

    def initConfigPanel(self):
        self.configPanel = tk.Toplevel(self)
        self.configPanel.title(f"{self.name}测试参数设置")
        self.configPanelFr1 = tk.Frame(self.configPanel)
        self.configPanelFr1.grid(row=1, column=1)
        tk.Label(self.configPanelFr1, text="预留该页面用于设定通用数据").grid(
            row=1, columnspan=2, column=1
        )

        self.configPanelIdLabel = tk.Label(
            self.configPanelFr1, text="模块MsgId设置")
        self.configPanelIdLabel.grid(row=2, column=1)
        self.idEntry = tk.Entry(self.configPanelFr1,
                                textvariable=self.idIntVar)
        self.idEntry.grid(row=2, column=2)

        self.configPanelRcevIdLabel = tk.Label(
            self.configPanelFr1, text="反馈报文Id设置")
        self.configPanelRcevIdLabel.grid(row=3, column=1)
        self.idEntry1 = tk.Entry(
            self.configPanelFr1, textvariable=self.idRcevIntvar)
        self.idEntry1.grid(row=3, column=2)

        self.configPanelLearmaskLabel = tk.Label(
            self.configPanelFr1, text='Learmask设置')
        self.configPanelLearmaskLabel.grid(row=4, column=1)
        self.idEntry2 = tk.Entry(
            self.configPanelFr1, textvariable=self.idIntLearmask)
        self.idEntry2.grid(row=4, column=2)

        self.configPanelFr2 = tk.Frame(self.configPanel)
        self.configPanelFr2.grid(row=2, column=1)
        self.configPanelFr2InfoTipsLabel = tk.Label(
            self.configPanelFr2, text="备份说明信息")
        self.configPanelFr2InfoTipsLabel.grid(row=1, column=1, columnspan=2)
        self.configPanelFr2InfoTipsText = tk.Text(
            self.configPanelFr2, width=50, height=10)
        self.configPanelFr2InfoTipsText.grid(row=2, column=1, columnspan=2)

        self.configPanelFr2infoOperatorName = tk.Label(
            self.configPanelFr2, text="备份人姓名:")
        self.configPanelFr2infoOperatorName.grid(
            row=3, column=1)
        self.configPanelFr2Operator = tk.StringVar()
        self.configPanelFr2Operator.set("xxxxxx")
        self.configPanelFr2OperatorEntry = tk.Entry(
            self.configPanelFr2, textvariable=self.configPanelFr2Operator
        )
        self.configPanelFr2OperatorEntry.grid(row=3, column=2)
        self.configPanelFr2BackupBtn = tk.Button(
            self.configPanelFr2, text="备份配置数据", command=lambda: self.backupConfig()
        )
        self.configPanelFr2BackupBtn.grid(row=4, column=1, columnspan=2)
        pass

    def backupConfig(self):
        askyesno = tk.messagebox.askyesno("确认", "确认要备份配置吗?")
        if askyesno is False:
            return
        nowConfigs = self.masterUI.autoUnitTestmodelDict

        # 当前的模块设定数据
        curConfig = nowConfigs[self.PosId]
        ic_debugger(curConfig)
        # dev_logger.debug(curConfig)
        # 发送报文id
        curConfig.MsgId = self.idIntVar.get()
        # 接收反馈报文id
        curConfig.rcevId = self.idRcevIntvar.get()
        # Learmask
        curConfig.Learmask = self.idIntLearmask.get()

        tempTestDatas = []
        # 通过key索引把原来改过的内容更新回去到设置里.
        for key, value in self.testDataDict.items():
            unittest = UnitTestData_Type(
                name=value['name'],
                send=self.sendDataStringDict[key].get(),
                rcev=self.rcevDataStringDict[key].get(),
            )
            tempTestDatas.append(unittest)
        curConfig.testDatas = tempTestDatas

        ic_debugger(curConfig)

        # 更新当前测试数据
        nowConfigs[self.PosId] = curConfig

        # 把pydantic类型转换为字典，后面才可以json.dump
        configList = []
        for testDT in nowConfigs.values():
            dictData = testDT.model_dump()
            configList.append(dictData)

        # 设定data数据
        self.masterUI.configJson["data"] = configList

        # 设定info数据
        self.masterUI.configJson["info"]["operator"] = self.configPanelFr2Operator.get(
        )
        self.masterUI.configJson["info"]["Tips"] = self.configPanelFr2InfoTipsText.get(
            "1.0", "end"
        )

        with open("config/backupConfig.json", mode="w", encoding="utf-8") as f:
            json.dump(self.masterUI.configJson, f, ensure_ascii=False)

        pass

    def close_safetyAccess(self):
        # 停止循环发送3e 00
        self.safetyAccess_Flag = False

    def open_safetyAccess(self):
        serveFunc: Serve_Func = GLOBAL_UDS_SERVE_FUNC_LIB
        response = serveFunc.serve_10_03(
            srcAddr=self.MsgId, dstAddr=self.rcevId)
        # ic_debugger(response)
        # 如果没有响应，response就是None直接返回，
        if response is None:
            return response
        if response.status == Uds_ResponseStatus_Type.ZCAN_UDS_ERROR_OK and response.type == Uds_ResponseType_Type.POSITIVE:
            response = serveFunc.serve_3e_once(
                srcAddr=self.MsgId, dstAddr=self.rcevId)

            def wrapper():
                while self.safetyAccess_Flag:
                    time.sleep(3)
                    serveFunc.serve_3e_once(
                        srcAddr=self.MsgId, dstAddr=self.rcevId)

            # [ ] 启动循环,待确认是否会因为函数运行完成而停止子进程
            threading.Thread(target=wrapper).start()

            if response.status == Uds_ResponseStatus_Type.ZCAN_UDS_ERROR_OK and response.type == Uds_ResponseType_Type.POSITIVE:
                response = serveFunc.serve_27_01_02(
                    srcAddr=self.MsgId, dstAddr=self.rcevId, Learmask=self.Learmask)

        # 在那个环节出现问题，就在那个环节停止，返回当前的response，备用
        return response

    def sendControlMsg(self, key):
        def wrapper():
            # serveFunc: Serve_Func = self.masterUI.TESTER.WORKFLOW.serveFunc
            serveFunc: Serve_Func = GLOBAL_UDS_SERVE_FUNC_LIB
            send_info = self.sendDataStringDict[key].get()
            rcev_info = self.rcevDataStringDict[key].get()

            # 构建请求参数
            send_data = send_info.split()
            req_sid = int(send_data[0], 16)
            req_src_addr = self.MsgId
            req_dst_addr = self.rcevId
            req_data_1 = [int(item, 16) for item in send_data[1:]]
            req_param_len = len(req_data_1)
            uds_request = Uds_Request_Type(req_src_addr=req_src_addr, req_dst_addr=req_dst_addr,
                                           req_sid=req_sid, req_param_len=req_param_len, req_data_1=req_data_1)

            # 发送请求获取响应
            uds_response = serveFunc.send_and_handle_uds_msg(
                uds_request=uds_request)

            ic_debugger(uds_response)
            # 标识发送完成label，虽然没有完成，但是仍然标识
            self.sendDataLabelDict[key].configure(background='green')

            # 核对响应
            if uds_response:

                # 标识接收完成label
                self.recvDataLabelDict[key].configure(background='green')

                if uds_response.response.sid == req_sid + 0x40:
                    # 绿色标识按钮
                    self.sendDataBtnDict[key].configure(background='green')
                    return True
                else:
                    self.sendDataBtnDict[key].configure(background='red')
                    return False
            else:
                self.sendDataBtnDict[key].configure(background='red')
                return False

        return wrapper

    def allTest(self):
        from collections import defaultdict
        result = defaultdict(int)
        for key, _ in enumerate(self.testDatas):
            if self.sendControlMsg(key)() is True:
                result['成功'] += 1
            else:
                result['失败'] += 1

        self.alltestResMsg.configure(text=f'成功： {result["成功"] if result["成功"] else 0};失败：{
                                     result["失败"] if result["失败"] else 0}')
        if result["失败"] == 0:
            self.alltestResMsg.configure(fg='green')
        else:
            self.alltestResMsg.configure(fg='red')
